home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 7
/
Aminet 7 - August 1995.iso
/
Aminet
/
comm
/
tcp
/
AmigaTCP.lha
/
AmigaTCP
/
src
/
tcpin.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-06-24
|
22KB
|
823 lines
/* Process incoming TCP segments. Page number references are to ARPA RFC-793,
* the TCP specification.
*/
#include "machdep.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "tcp.h"
#include "icmp.h"
#include "ip.h"
struct tcp_stat tcp_stat;
/* This function is called from IP with the IP header in machine byte order,
* along with a mbuf chain pointing to the TCP header.
*/
void
tcp_input(bp,protocol,source,dest,tos,length,rxbroadcast)
struct mbuf *bp; /* Data field, if any */
char protocol; /* Should always be TCP_PTCL */
int32 source; /* Remote IP address */
int32 dest; /* Our IP address */
char tos; /* Type of Service */
int16 length; /* Length of data field */
char rxbroadcast; /* Incoming broadcast - discard if true */
{
void reset(),update();
void proc_syn(),send_syn(),add_reseq(),get_reseq();
register struct tcb *tcb; /* TCP Protocol control block */
struct tcp_header seg; /* Local copy of segment header */
int16 mss; /* Incoming MSS, if any */
struct connection conn; /* Local copy of addresses */
struct pseudo_header ph; /* Pseudo-header for checksumming */
int16 optlen; /* Length of TCP options */
if(bp == NULLBUF)
return;
if(rxbroadcast){
/* Any TCP packet arriving as a broadcast is
* to be completely IGNORED!!
*/
tcp_stat.bdcsts++;
free_p(bp);
}
ph.source = source;
ph.dest = dest;
ph.protocol = protocol;
ph.zero = 0;
ph.length = length;
if(cksum(&ph,bp,length) != 0){
/* Checksum failed, ignore segment completely */
tcp_stat.checksum++;
free_p(bp);
return;
}
/* Form local copy of TCP header in host byte order */
if(pullup(&bp,(char *)&seg,sizeof(struct tcp_header))
!= sizeof(struct tcp_header)){
/* TCP header is too small */
tcp_stat.runt++;
free_p(bp);
return;
}
length -= sizeof(struct tcp_header);
seg.source = ntohs(seg.source);
seg.dest = ntohs(seg.dest);
seg.seq = ntohl(seg.seq);
seg.ack = ntohl(seg.ack);
seg.wnd = ntohs(seg.wnd);
seg.up = ntohs(seg.up); /* Unused as of yet */
/* Examine options, if any (there can only be MSS). */
optlen = hinibble(seg.offset) * sizeof(long) - sizeof(struct tcp_header);
if(optlen != 0){
struct mss *mssp;
mssp = (struct mss *)bp->data;
if(mssp->kind == MSS_KIND && mssp->length == MSS_LENGTH){
mss = ntohs(mssp->mss);
}
pullup(&bp,NULLCHAR,optlen);
length -= optlen;
} else {
mss = 0;
}
/* Fill in connection structure and find TCB */
conn.local.address = dest;
conn.local.port = seg.dest;
conn.remote.address = source;
conn.remote.port = seg.source;
if((tcb = lookup_tcb(&conn)) == NULLTCB){
struct tcb *ntcb;
char *malloc();
void link_tcb();
/* Check that this segment carries a SYN, and that
* there's a LISTEN on this socket with
* unspecified source address and port
*/
conn.remote.address = 0;
conn.remote.port = 0;
if(!(seg.flags & SYN) || (tcb = lookup_tcb(&conn)) == NULLTCB){
/* No unspecified LISTEN either, so reject */
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
/* We've found an unspecified remote socket, so clone the TCB
* and stuff the foreign socket into the clone.
*/
if((ntcb = (struct tcb *)malloc(sizeof (struct tcb))) == NULLTCB){
free_p(bp);
/* This may fail, but we should at least try */
reset(source,dest,tos,length,&seg);
return;
}
bcopy((char *)tcb,(char *)ntcb,sizeof(struct tcb));
tcb = ntcb;
tcb->conn.remote.address = source;
tcb->conn.remote.port = seg.source;
tcb->timer.arg = (int *)tcb;
link_tcb(tcb);
}
/* Do unsynchronized-state processing (p. 65-68) */
switch(tcb->state){
case CLOSED:
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
case LISTEN:
if(seg.flags & RST){
free_p(bp);
return;
}
if(seg.flags & ACK){
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
if(seg.flags & SYN){
/* (Security check is bypassed) */
/* page 66 */
tcp_stat.conin++;
proc_syn(tcb,tos,&seg,mss);
send_syn(tcb);
setstate(tcb,SYN_RECEIVED);
if(length != 0 || seg.flags & FIN) {
break; /* Continue processing if there's more */
}
tcp_output(tcb);
}
free_p(bp); /* Unlikely to get here directly */
return;
case SYN_SENT:
if(seg.flags & ACK){
if(!seq_within(seg.ack,tcb->iss+1,tcb->snd.nxt)){
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
}
if(seg.flags & RST){ /* p 67 */
if(seg.flags & ACK){
/* The ack must be acceptable since we just checked it.
* This is how the remote side refuses connect requests.
*/
close_self(tcb,RESET);
}
free_p(bp);
return;
}
/* (Security check skipped here) */
/* Check incoming precedence; it must match if there's an ACK */
if((seg.flags & ACK) && PREC(tos) != PREC(tcb->tos)){
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
if(seg.flags & SYN){
proc_syn(tcb,tos,&seg,mss);
if(seg.flags & ACK){
/* Our SYN has been acked, otherwise the ACK
* wouldn't have been valid.
*/
update(tcb,&seg);
setstate(tcb,ESTABLISHED);
} else {
setstate(tcb,SYN_RECEIVED);
}
if(length != 0 || (seg.flags & FIN)) {
break; /* Continue processing if there's more */
}
tcp_output(tcb);
} else {
free_p(bp); /* Ignore if neither SYN or RST is set */
}
return;
}
/* We reach this point directly in any synchronized state. Note that
* if we fell through from LISTEN or SYN_SENT processing because of a
* data-bearing SYN, window trimming and sequence testing "cannot fail".
*/
/* Trim segment to fit receive window. */
if(trim(tcb,&seg,&bp,&length) == -1){
/* Segment is unacceptable */
if(!(seg.flags & RST)){
tcb->force = 1;
tcp_output(tcb);
}
return;
}
/* If segment isn't the next one expected, and there's data
* or flags associated with it, put it on the resequencing
* queue and return. Don't send anything in reply.
*
* Processing the ACK in an out-of-sequence segment without
* flags or data should be safe, however.
*/
if(seg.seq != tcb->rcv.nxt
&& (length != 0 || (seg.flags & (SYN|FIN)) )){
add_reseq(tcb,tos,&seg,bp,length);
return;
}
/* This loop first processes the current segment, and then
* repeats if it can process the resequencing queue.
*/
for(;;){
/* We reach this point with an acceptable segment; all data and flags
* are in the window, and the starting sequence number equals rcv.nxt
* (p. 70)
*/
if(seg.flags & RST){
switch(tcb->state){
case SYN_RECEIVED:
setstate(tcb,LISTEN);
free_p(bp);
return;
default:
close_self(tcb,RESET);
free_p(bp);
return;
}
}
/* (Security check skipped here) p. 71 */
/* Check for precedence mismatch or erroneous extra SYN */
if(PREC(tos) != PREC(tcb->tos) || (seg.flags & SYN)){
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
/* Check ack field p. 72 */
if(!(seg.flags & ACK)){
free_p(bp); /* All segments after synchronization must have ACK */
return;
}
/* Process ACK */
switch(tcb->state){
case SYN_RECEIVED:
if(seq_within(seg.ack,tcb->snd.una+1,tcb->snd.nxt)){
update(tcb,&seg);
setstate(tcb,ESTABLISHED);
} else {
free_p(bp);
reset(source,dest,tos,length,&seg);
return;
}
break;
case ESTABLISHED:
case CLOSE_WAIT:
update(tcb,&seg);
break;
case FINWAIT1: /* p. 73 */
update(tcb,&seg);
if(tcb->sndcnt == 0){
/* Our FIN is acknowledged */
setstate(tcb,FINWAIT2);
}
break;
case FINWAIT2:
update(tcb,&seg);
break;
case CLOSING:
update(tcb,&seg);
if(tcb->sndcnt == 0){
/* Our FIN is acknowledged */
setstate(tcb,TIME_WAIT);
tcb->timer.start = MSL2;
start_timer(&tcb->timer);
}
break;
case LAST_ACK:
update(tcb,&seg);
if(tcb->sndcnt == 0){
/* Our FIN is acknowledged, close connection */
close_self(tcb,NORMAL);
return;
}
case TIME_WAIT:
tcb->force = 1;
start_timer(&tcb->timer);
}
/* (URGent bit processing skipped here) */
/* Process the segment text, if any, beginning at rcv.nxt (p. 74) */
if(length != 0){
switch(tcb->state){
case SYN_RECEIVED:
case ESTABLISHED:
case FINWAIT1:
case FINWAIT2:
/* Place on receive queue */
append(&tcb->rcvq,bp);
tcb->rcvcnt += length;
tcb->rcv.nxt += length;
tcb->rcv.wnd -= length;
tcb->force = 1;
break;
default:
/* Ignore segment text */
free_p(bp);
break;
}
}
/* If the user has set up a r_upcall function, AND
* the receive window is more than half full OR
* has the urg, push or fin flags set, notify him.
*
* All this is done before sending an acknowledgement,
* to give the user a chance to piggyback some reply data.
* It's also done before processing FIN so that the state
* change upcall will occur after the user has had a chance
* to read the last of the incoming data.
*/
if(tcb->r_upcall){
if(tcb->rcvcnt >= tcb->rcv.wnd || (seg.flags & (FIN|URG|PSH))){
(*tcb->r_upcall)(tcb,tcb->rcvcnt);
}
}
/* process FIN bit (p 75) */
if(seg.flags & FIN){
tcb->force = 1; /* Always respond with an ACK */
switch(tcb->state){
case SYN_RECEIVED:
case ESTABLISHED:
tcb->rcv.nxt++;
setstate(tcb,CLOSE_WAIT);
break;
case FINWAIT1:
tcb->rcv.nxt++;
if(tcb->sndcnt == 0){
/* Our FIN has been acked; bypass CLOSING state */
setstate(tcb,TIME_WAIT);
tcb->timer.start = MSL2;
start_timer(&tcb->timer);
} else {
setstate(tcb,CLOSING);
}
break;
case FINWAIT2:
tcb->rcv.nxt++;
setstate(tcb,TIME_WAIT);
tcb->timer.start = MSL2;
start_timer(&tcb->timer);
break;
case CLOSE_WAIT:
case CLOSING:
case LAST_ACK:
break; /* Ignore */
case TIME_WAIT: /* p 76 */
start_timer(&tcb->timer);
break;
}
}
/* Scan the resequencing queue, looking for a segment we can handle,
* and freeing all those that are now obsolete.
*/
while(tcb->reseq != NULLRESEQ && seq_ge(tcb->rcv.nxt,tcb->reseq->seg.seq)){
get_reseq(tcb,&tos,&seg,&bp,&length);
if(trim(tcb,&seg,&bp,&length) == 0)
goto gotone;
/* Segment is an old one; trim has freed it */
}
break;
gotone: ;
}
tcp_output(tcb); /* Send any necessary ack */
}
/* Process an incoming ICMP response */
tcp_icmp(source,dest,type,code,data)
int32 source; /* Original IP datagram source (i.e. us) */
int32 dest; /* Original IP datagram dest (i.e., them) */
char type,code; /* ICMP error codes */
char *data; /* First 8 bytes of TCP header */
{
struct tcp_header *tcph;
struct connection conn;
register struct tcb *tcb;
/* Extract the socket info from the returned TCP header fragment
* Note that since this is a datagram we sent, the source fields
* refer to the local side.
*/
tcph = (struct tcp_header *)data;
conn.local.address = source;
conn.local.port = ntohs(tcph->source);
conn.remote.address = dest;
conn.remote.port = ntohs(tcph->dest);
if((tcb = lookup_tcb(&conn)) == NULLTCB)
return; /* Unknown connection, ignore */
/* Verify that the sequence number in the returned segment corresponds
* to something currently unacknowledged. If not, it can safely
* be ignored.
*/
if(!seq_within(ntohl(tcph->seq),tcb->snd.una,tcb->snd.nxt))
return;
/* The strategy here is that Destination Unreachable and Time Exceeded
* messages that occur after a connection has been established are likely
* to be transient events, and shouldn't kill our connection (at least
* until after we've tried a few more times). On the other hand, if
* they occur on our very first attempt to send a datagram on a new
* connection, they're probably "for real". In any event, the info
* is saved.
*/
switch(type){
case DEST_UNREACH:
case TIME_EXCEED:
tcb->type = type;
tcb->code = code;
if(tcb->state == SYN_SENT || tcb->state == SYN_RECEIVED){
close_self(tcb,NETWORK);
}
break;
case QUENCH:
break; /* I really ought to implement this */
}
}
/* Send an acceptable reset (RST) response for this segment */
static void
reset(source,dest,tos,length,seg)
int32 source; /* Remote IP address */
int32 dest; /* Our IP address */
char tos; /* Type of Service */
int16 length; /* Length of data portion */
register struct tcp_header *seg; /* Offending TCP header */
{
struct mbuf *hbp;
struct pseudo_header ph;
register struct tcp_header *tcph;
if(seg->flags & RST)
return; /* Never send an RST in response to an RST */
tcp_stat.resets++;
/* Compose the RST IP pseudo-header, swapping addresses */
ph.source = dest;
ph.dest = source;
ph.protocol = TCP_PTCL;
ph.zero = 0;
ph.length = sizeof(struct tcp_header);
/* Allocate mbuf for the RST TCP header and fill it in */
if((hbp = alloc_mbuf(ph.length)) == NULLBUF)
return; /* Can't do nothin' */
hbp->cnt = ph.length;
tcph = (struct tcp_header *)hbp->data;
tcph->source = htons(seg->dest);
tcph->dest = htons(seg->source);
tcph->flags = RST;
if(seg->flags & ACK){
/* This reset is being sent to clear a half-open connection.
* Set the sequence number of the RST to the incoming ACK
* so it will be acceptable.
*/
tcph->seq = htonl(seg->ack);
tcph->ack = 0;
} else {
/* We're rejecting a connect request (SYN) from LISTEN state
* so we have to "acknowledge" their SYN.
*/
tcph->seq = 0;
if(seg->flags & SYN)
length++;
if(seg->flags & FIN)
length++;
tcph->ack = htonl((int32)(seg->seq + length));
tcph->flags |= ACK;
}
tcph->offset = (ph.length/sizeof(long)) << DSHIFT;
tcph->wnd = 0;
tcph->checksum = 0;
tcph->up = 0;
tcph->checksum = cksum(&ph,hbp,ph.length);
/* Ship it out (note swap of addresses) */
ip_send(dest,source,TCP_PTCL,tos,0,hbp,ph.length,0,0);
}
/* Process an incoming acknowledgement and window indication.
* From page 72.
*/
static void
update(tcb,seg)
register struct tcb *tcb;
register struct tcp_header *seg;
{
int16 acked;
int32 rtt;
acked = 0;
if(seq_gt(seg->ack,tcb->snd.nxt)){
tcb->force = 1; /* Acks something not yet sent */
return;
}
/* Round trip time estimation */
if(seq_gt(tcb->rttseq,tcb->snd.una)){
/* A sequence number is actively being timed */
if(tcb->retry == 0 && seq_ge(seg->ack,tcb->rttseq)){
/* This packet was sent only once and now
* it's been acked, so compute the new smoothed
* estimate. The RT time is just the current time
* on the retransmission timer.
*/
rtt = tcb->timer.start - tcb->timer.count;
rtt *= MSPTICK; /* convert to ticks */
/* Fast attack/slow decay algorithm by Dave Mills
* (see RFC-889)
*/
if(rtt > tcb->srtt){
/* RTT is increasing, use fast attack */
tcb->srtt = (ALPHA1*tcb->srtt + rtt)/(ALPHA1+1);
} else {
/* RTT is decreasing, use slow decay */
tcb->srtt = (ALPHA2*tcb->srtt + rtt)/(ALPHA2+1);
}
/* Set new retransmission timeout */
tcb->timer.start = (BETA * tcb->srtt)/MSPTICK;
}
}
/* If the remote window is closed, reset the retry counter even if
* this packet doesn't acknowledge anything new as long as the ACK
* is current.
* This allows closed-window probes to go on indefinitely.
*/
if(seg->wnd == 0 && seq_ge(seg->ack,tcb->snd.una))
tcb->retry = 0;
/* See if anything new is being acknowledged */
if(seq_gt(seg->ack,tcb->snd.una)){
acked = seg->ack - tcb->snd.una;
/* If we're waiting for an ack of our SYN, process it */
switch(tcb->state){
case SYN_SENT:
case SYN_RECEIVED:
acked--;
tcb->sndcnt--;
}
/* Remove acknowledged bytes from the send queue and update the
* unacknowledged pointer. If a FIN is being acked,
* pullup won't be able to remove it from the queue.
*/
pullup(&tcb->sndq,NULLCHAR,acked);
/* This will include the FIN if there is one */
tcb->sndcnt -= acked;
tcb->snd.una = seg->ack;
/* If retransmissions have been occurring, make sure the
* send pointer doesn't repeat ancient history
*/
if(seq_lt(tcb->snd.ptr,tcb->snd.una))
tcb->snd.ptr = tcb->snd.una;
/* Reset the retry counter */
tcb->retry = 0;
/* Stop retransmission timer, but restart it if there is still
* unacknowledged data
*/
stop_timer(&tcb->timer);
if(tcb->snd.una != tcb->snd.nxt){
start_timer(&tcb->timer);
}
}
/* Decide if we need to do a window update.
* This is always checked whenever a legal ACK is received,
* because it might be a spontaneous window reopening.
*/
if(seq_gt(seg->seq,tcb->snd.wl1) || ((seg->seq == tcb->snd.wl1)
&& seq_ge(seg->ack,tcb->snd.wl2))){
/* If the window had been closed, crank back the
* send pointer so we'll immediately resume transmission.
* Otherwise we'd have to wait until the next probe.
*/
if(tcb->snd.wnd == 0 && seg->wnd != 0)
tcb->snd.ptr = tcb->snd.una;
tcb->snd.wnd = seg->wnd;
tcb->snd.wl1 = seg->seq;
tcb->snd.wl2 = seg->ack;
}
/* If outgoing data was acked, notify the user so he can send more
* unless we've already sent a FIN.
*/
if(acked != 0 && tcb->t_upcall){
switch(tcb->state){
case ESTABLISHED:
case CLOSE_WAIT:
(*tcb->t_upcall)(tcb,tcb->window - tcb->sndcnt);
}
}
}
/* Determine if the given sequence number is in our receiver window.
* NB: must not be used when window is closed!
*/
static
int
in_window(tcb,seq)
struct tcb *tcb;
int32 seq;
{
return seq_within(seq,tcb->rcv.nxt,(int32)(tcb->rcv.nxt+tcb->rcv.wnd-1));
}
/* Process an incoming SYN */
static void
proc_syn(tcb,tos,seg,mss)
register struct tcb *tcb;
char tos;
struct tcp_header *seg;
int16 mss;
{
int16 mtu,ip_mtu();
tcb->force = 1; /* Always send a response */
/* Note: It's not specified in RFC 793, but SND.WL1 and
* SND.WND are initialized here since it's possible for the
* window update routine in update() to fail depending on the
* IRS if they are left unitialized.
*/
/* Check incoming precedence and increase if higher */
if(PREC(tos) > PREC(tcb->tos))
tcb->tos = tos;
tcb->rcv.nxt = seg->seq + 1; /* p 68 */
tcb->snd.wl1 = tcb->irs = seg->seq;
tcb->snd.wnd = seg->wnd;
if(mss != 0)
tcb->mss = mss;
/* Check the MTU of the interface we'll use to reach this guy
* and lower the MSS so that unnecessary fragmentation won't occur
*/
if((mtu = ip_mtu(tcb->conn.remote.address)) != 0){
/* Allow space for the TCP and IP headers */
mtu -= sizeof(struct tcp_header) + sizeof(struct ip_header);
tcb->mss = min(mtu,tcb->mss);
}
}
/* Generate an initial sequence number and put a SYN on the send queue */
void
send_syn(tcb)
register struct tcb *tcb;
{
tcb->iss = iss();
tcb->rttseq = tcb->snd.ptr = tcb->snd.wl2 = tcb->snd.nxt = tcb->snd.una = tcb->iss;
tcb->sndcnt++;
tcb->force = 1;
}
/* Add an entry to the resequencing queue in the proper place */
static void
add_reseq(tcb,tos,seg,bp,length)
struct tcb *tcb;
char tos;
struct tcp_header *seg;
struct mbuf *bp;
int16 length;
{
register struct reseq *rp,*rp1;
char *malloc();
/* Allocate reassembly descriptor */
if((rp = (struct reseq *)malloc(sizeof (struct reseq))) == NULLRESEQ){
/* No space, toss on floor */
free_p(bp);
return;
}
bcopy((char *)seg,(char *)&rp->seg,sizeof(struct tcp_header));
rp->tos = tos;
rp->bp = bp;
rp->length = length;
/* Place on reassembly list sorting by starting seq number */
rp1 = tcb->reseq;
if(rp1 == NULLRESEQ || seq_lt(seg->seq,rp1->seg.seq)){
/* Either the list is empty, or we're less than all other
* entries; insert at beginning.
*/
rp->next = rp1;
tcb->reseq = rp;
} else {
/* Find the last entry less than us */
for(;;){
if(rp1->next == NULLRESEQ || seq_lt(seg->seq,rp1->next->seg.seq)){
/* We belong just after this one */
rp->next = rp1->next;
rp1->next = rp;
break;
}
rp1 = rp1->next;
}
}
}
/* Fetch the first entry off the resequencing queue */
static void
get_reseq(tcb,tos,seg,bp,length)
register struct tcb *tcb;
char *tos;
struct tcp_header *seg;
struct mbuf **bp;
int16 *length;
{
register struct reseq *rp;
if((rp = tcb->reseq) == NULLRESEQ)
return;
tcb->reseq = rp->next;
*tos = rp->tos;
bcopy((char *)&rp->seg,(char *)seg,sizeof(struct tcp_header));
*bp = rp->bp;
*length = rp->length;
free((char *)rp);
}
/* Trim segment to fit window. Return 0 if OK, -1 if segment is
* unacceptable.
*/
static int
trim(tcb,seg,bp,length)
register struct tcb *tcb;
register struct tcp_header *seg;
struct mbuf **bp;
int16 *length;
{
struct mbuf *nbp;
long dupcnt,excess;
int16 len; /* Segment length including flags */
char accept;
accept = 0;
len = *length;
if(seg->flags & SYN)
len++;
if(seg->flags & FIN)
len++;
/* Acceptability tests */
if(tcb->rcv.wnd == 0){
/* Only in-order, zero-length segments are acceptable when our window
* is closed.
*/
if(seg->seq == tcb->rcv.nxt && len == 0){
return 0; /* Acceptable, no trimming needed */
}
} else {
/* Some part of the segment must be in the window */
if(in_window(tcb,seg->seq)){
accept++; /* Beginning is */
} else if(len != 0){
if(in_window(tcb,(int32)(seg->seq+len-1)) || /* End is */
seq_within(tcb->rcv.nxt,seg->seq,(int32)(seg->seq+len-1))){ /* Straddles */
accept++;
}
}
}
if(!accept){
free_p(*bp);
return -1;
}
dupcnt = tcb->rcv.nxt - seg->seq;
if(dupcnt > 0){
/* Trim off SYN if present */
if(seg->flags & SYN){
/* SYN is before first data byte */
seg->flags &= ~SYN;
seg->seq++;
dupcnt--;
}
if(dupcnt > 0){
pullup(bp,NULLCHAR,(int16)dupcnt);
seg->seq += dupcnt;
*length -= dupcnt;
}
}
excess = seg->seq + *length - (tcb->rcv.nxt + tcb->rcv.wnd);
if(excess > 0){
/* Trim right edge */
*length -= excess;
nbp = copy_p(*bp,*length);
free_p(*bp);
*bp = nbp;
seg->flags &= ~FIN; /* FIN follows last data byte */
}
return 0;
}